package com.tosee.dao.hibernate.impl;

import java.io.Serializable;
import java.util.List;

import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.hibernate3.HibernateTemplate;

import com.tosee.dao.hibernate.IHibernateGenericDao;
import com.tosee.exception.AccesConcurrentException;
import com.tosee.exception.ViolationContrainteException;

/**
 * Implémentation Hibernate du {@link IHibernateGenericDao}.
 * 
 * @author FabienC
 * @param <T> Classe de l'entité gérée par Hibernate
 * @param <K> Type de la clé de l'entité
 */
public class HibernateGenericDao<T, K extends Serializable> implements IHibernateGenericDao<T, K> {

    /**
     * Hibernate Template.
     */
    private final HibernateTemplate hibernateTemplate;

    /**
     * Classe de l'entité gérée par ce DAO.
     */
    private final Class<T> clazz;

    /**
     * Constructeur initialisant ce DAO.
     * 
     * @param clazz la classe de l'entité persistée
     * @param sessionFactory session Hibernate utilisée pour la persistance
     */
    public HibernateGenericDao(Class<T> clazz, SessionFactory sessionFactory) {
        this.clazz = clazz;
        hibernateTemplate = new HibernateTemplate(sessionFactory);
    }

    /**
     * {@inheritDoc}
     */
    public void save(T entity) throws ViolationContrainteException {
        try {
            hibernateTemplate.save(entity);
            hibernateTemplate.flush();
        } catch (DataIntegrityViolationException e) {
            throw new ViolationContrainteException("Violation de contrainte. Echec de l'enregistrement de " + entity, e);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void delete(T entity) throws ViolationContrainteException, AccesConcurrentException {
        try {
            hibernateTemplate.delete(entity);
            hibernateTemplate.flush();
        } catch (DataIntegrityViolationException e) {
            throw new ViolationContrainteException("Violation de contrainte. Echec de l'enregistrement de " + entity, e);
        } catch (ConcurrencyFailureException e) {
            throw new AccesConcurrentException("Accès concurrent. Echec de l'enregistrement de " + entity, e);
        }

    }

    /**
     * {@inheritDoc}
     */
    public void update(T entity) throws ViolationContrainteException, AccesConcurrentException {
        try {
            hibernateTemplate.update(entity);
            hibernateTemplate.flush();
        } catch (DataIntegrityViolationException e) {
            throw new ViolationContrainteException("Violation de contrainte. Echec de l'enregistrement de " + entity, e);
        } catch (ConcurrencyFailureException e) {
            throw new AccesConcurrentException("Accès concurrent. Echec de l'enregistrement de " + entity, e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public T findById(K id) {
        return (T) hibernateTemplate.get(clazz, id);
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public List<T> findAll() {
        return hibernateTemplate.loadAll(clazz);
    }

    /**
     * @return {@link HibernateTemplate} utilisable dans les sous-classes
     */
    protected HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }

    /**
     * Recherche d'une valeur unique depuis un criteria.
     * 
     * @param criteria critère de recherche
     * @return l'entité unique retournée, ou null si aucun résultat
     */
    @SuppressWarnings("unchecked")
    protected T findUniqueByCriteria(DetachedCriteria criteria) {

        List<T> list = hibernateTemplate.findByCriteria(criteria);

        if(list.isEmpty()) {
            return null;
        }

        return list.get(0);
    }

    /**
     * Création d'un {@link DetachedCriteria} initialisé sur la classe de l'entité.
     * 
     * @return un {@link DetachedCriteria} prêt à l'emploi
     */
    protected DetachedCriteria createCriteria() {
        return DetachedCriteria.forClass(clazz);
    }

    /**
     * Recherche d'une liste d'élément de type T à partir d'un critéria.
     * 
     * @param criteria criteria
     * @return liste de T
     */
    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(DetachedCriteria criteria) {
        return hibernateTemplate.findByCriteria(criteria);
    }
}
